﻿#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"

//设置光标的起始位置函数
void SetPos(short x, short y)
{
	//获取标准输出的句柄（HANDLE是void* 类型）
	HANDLE houtput = NULL; 
	houtput=GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置(为了后面好修改起始位置  定义一个起始位置  用x y进行传参)
	COORD pos = { x,y };   //COORD 参数   定义一个pos变量  表示坐标位置    默认光标位置0  可以用COORD设置 
	SetConsoleCursorPosition(houtput, pos); 
}

//打印欢迎界面（定位光标位置）
void WelcomeToGame()
{
	SetPos(36, 12);
	wprintf(L"欢迎来到贪吃的小叶小游戏！\n");
	SetPos(40, 14);
	system("pause");
	system("cls");
	SetPos(33, 10);
	wprintf(L"请用← ↑ ↓ →进行操控哦 (●—●)\n");
	SetPos(35, 12);
	wprintf(L"使用F3控制加速  F4控制减速哦\n");
	SetPos(38, 14);
	wprintf(L"* 加速后的分值更高哦 *\n");
	SetPos(40, 16);
	system("pause");
	system("cls");
}

//构建地图(58列  27行    横向29个 纵向27个方块)
void CreateMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i);  //从56开始
		wprintf(L"%lc", WALL);
	}
}

//创建蛇(初始化蛇  五个节点  串起来打印在具体位置)
void InitSnake(pSnake ps)   //传的是结构体指针  故这里直接传变量ps就行
{
	//从默认位置（24,5）开始  蛇身默认5节点  游戏状态OK  移动速度200ms  默认方向RIGHT  初始成绩0  每个食物分数10
	int i = 0;
	//创建指针节点类型结构体
	pSnakeNode cur = NULL;
	for (i = 0; i < 5; i++)
	{
		cur=(pSnakeNode)malloc(sizeof(SnakeNode));
		//判空  开辟失败
		if (cur == NULL)
		{
			perror("InitSnake()::malloc()");
			return;
		}
	//开辟成功
		cur->next = NULL;   //最初节点（24，5）下一个节点先置为空
		cur->x = POS_X + 2 * i;   //其实x位置24  后续循环五次刚好就是向后加8
		cur->y = POS_Y;        //y坐标

	//运用头插的方法进行插入节点（指向蛇头的节点是 _pSnake）
		//链表为空
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = cur;
		}
		//链表非空
		else
		{
			cur->next = ps->_pSnake;  //节点的next指针指向头
			ps->_pSnake = cur;    //重新赋值节点的头为新节点
		}
	}
		cur = ps->_pSnake;    //让链表头结点的地址给cur（右边第一个节点）
		while (cur)     //开循环  进循环条件是cur 不为空(遍历链表)
		{
			SetPos(cur->x, cur->y);
			wprintf(L"%lc", BODY);
			cur = cur->next;
		}
		//蛇身打印完成  设置贪吃蛇的起始属性
		ps->_dir = RIGHT;    //默认向右
		ps->_score = 0;      //起始分数
		ps->_food_weight = 10;   //吃一个食物的分数
		ps->_sleep_time = 200;   //设置休眠的时间   单位是毫秒
		ps->_status = OK;      //起始状态是OK
		
}

//创建食物（食物的x的坐标必须为2的倍数  食物坐标在墙体内  食物坐标不能与蛇身的每个节点重复） 因为宽字符占两个字符  蛇身也应该是2的倍数起始
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
//注意：x坐标应该在2-54范围内（0-52 后（%53）+2 得到2-54）     y坐标应该在1-25范围内（0-24 后（%25）+1 得到1-25）
	//x = rand() % 53 + 2;
	//y = rand() % 25 + 1;     注意：x应为2的倍数   故定义循环  不满足重新生成
	
	//随机生成x  y  坐标
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1; 
	} while (x%2!=0);

	//x  y坐标也不能和蛇的身体坐标冲突
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;     //如果重复就回到again位置 然后重新生成
		}
		cur=cur->next;
	}
	//没有问题  创建食物节点
	pSnakeNode pFood=(pSnakeNode)malloc(sizeof(SnakeNode));   //创建节点pFood
	if(pFood == NULL)
	{
		perror("malloc");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;
	SetPos(x, y);
	wprintf(L"%lc", Food);
	ps->_pFood = pFood;    //把pFood的信息赋给ps->_pFood中   这样就让Food的信息也给ps维护起来了  
}

//打印帮助信息
void PrintHelpInfo()
{
	SetPos(64,14);
	wprintf(L"%ls", L"**不可以穿墙和咬到自己哦**");
	SetPos(64, 16);
	wprintf(L"%ls", L"  F3控制加速  F4控制减速");
	SetPos(64, 18);
	wprintf(L"%ls", L"   ← ↑ ↓ →进行操控 ");
	SetPos(64, 20);
	wprintf(L"%ls", L"     空格：暂停游戏    ");
	SetPos(64, 22);
	wprintf(L"%ls", L"     Esc ：退出游戏    ");
	SetPos(64, 25);
	wprintf(L"%ls", L"版权所属   李昌贤@叶齐娇");
}

//检测按键
//对获取按键情况进行封装（定义宏） 结果1表示按过 结果0表示没按过（为了方便调用  封装宏）
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)    //vk为要检验的值


//实现游戏的初始化
void GameStart(pSnake ps)    //函数传的是Snake结构体的地址  需要对结构体进行修改  故用结构体指针pSnake 进行接收  ps是变量
{
//1.设置窗口大小 名字   2.光标隐藏   3.打印欢迎界面（定位光标位置） 4.打印功能介绍   5.绘制地图   6.创建蛇   7.创建食物 
	
	//设置窗口大小  名字
	system("mode con cols=100 lines=30");
	system("title 贪吃的小叶");
	HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
	//隐藏光标
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(houtput, &CursorInfo);
	
	//打印欢迎界面（定位光标位置） 打印功能介绍
	WelcomeToGame();

	//构建地图
	CreateMap();

	//创建蛇
	InitSnake(ps);

	//创建食物
	CreateFood(ps);   //因为最终要把食物信息也放到结构体指针ps指向的pfood节点里面  故创建食物也要传参数  结点要进行记录下来
}

//函数暂停操作
void Pause()
{
	while (1)
	{
		//暂停操作让其进入死循环睡眠状态
		Sleep(200);
		//打破暂停睡眠状态(睡眠中若按下空格  就打破循环)
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

//判断下一节点是否是食物的函数
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return(ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}

//下一个节点是食物  吃掉食物的函数（头插法把节点挂上链表）
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插
	ps->_pFood->next = ps->_pSnake;    //食物节点的next  存蛇头地址
	ps->_pSnake = ps->_pFood;     //食物节点赋给头结点

	//进行释放下一个位置的节点
	free(pn);
	pn = NULL;

	//吃过之后要进行对蛇再一次遍历打印（因为链表已经多了一个节点）
	pSnakeNode cur = ps->_pSnake;  //蛇头地址赋给cur
	while (cur)     //进行循环遍历打印蛇
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//吃了食物  总分要进行加分
	ps->_score += ps->_food_weight;

	//吃了食物  需要重新创建一个食物
	CreateFood(ps);
}

//下一个节点不是食物的情况
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插 把下一个节点进行插入蛇身  用两个空格代替原尾结点 对最后一个节点进行释放 且倒数第二节点的next指针要置为空（最后一步）否则就成为了野指针
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//定位到最后cur->next(最后一个节点)结点的位置   然后对应位置输出两个空格
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	//释放最后一个节点
	free(cur->next);
	//把倒数第二个节点的地址置为空（这样就不会继续往下找）
	cur->next = NULL;
}

void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}

void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

//向上：坐标变为（x，y-1）  向下：（x,y+1）  向左(x-2,y)   向右（x+2，y）      都是下一个节点的位置(x一格是1字节  y一格是2字节  所以x+2)
//蛇的移动函数
void SnakeMove(pSnake ps)
{
	//因为蛇走的过程中（需要下一个节点的位置  故定义一个专门的下一个节点进行记录节点信息）   pNextNode  表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");   //表示在SnakeMove（）的malloc处出现问题
		return;
	}

	//要对移动方向进行检测（才能得知下一步是哪）  
	switch (ps->_dir)
	{
	case UP:
		pNextNode->x = ps->_pSnake->x;		   //给新节点坐标赋值
		pNextNode->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnake->x + 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	}
	//到这要判断下一个位置是不是食物(检测下一个坐标处  是食物吃掉/不是食物)
	if (NextIsFood(pNextNode, ps))    //要传下一个节点的地址  要把ps传过去（因为ps记录着食物的位置）
	{
		//是食物
		EatFood(pNextNode, ps);
		//pNextNode = NULL;
	}
	else
	{
		//不是食物
		NoFood(pNextNode, ps);
	}
	//检测蛇是否撞墙
	KillByWall(ps);     //涉及到游戏状态的修改  故需要传ps进去
	//检测蛇是否撞到自己
	KillBySelf(ps);
}

//实现游戏的运行的逻辑（走一步看一下游戏状态）
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	//进去就需要进行检测按键情况
	do
	{
		//打印总分数和食物的分值
		SetPos(64, 7);
		printf("      总 分 数 ：%d\n", ps->_score);
		SetPos(64, 9);
		printf("      此食物分数：%2d\n", ps->_food_weight);    //分数要按照两位数进行打印  不然会导致输出一位数覆盖掉源数据


		//检验按键
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)   //按键为向上  并且运行方向不为向下
		{
			ps->_dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
		{
			ps->_dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停操作
			Pause();    //这是暂停函数（暂停操作实际就是sleep）
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			ps->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速操作（每一次休眠时间降低  一开始是200ms  不能一直减少休眠  不能为负  故需要做判断）
			if(ps->_sleep_time>100)
			{
			ps->_sleep_time -= 20;
			ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速操作(可以用休眠时间进行控制  也可以用食物分数进行控制)
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 20;
				ps->_food_weight -= 2;
			}
		}
		//判断结束  开始运行（走完之后有没有撞到墙和自己）
		SnakeMove(ps);   //移动函数(蛇走一步的过程)  会影响蛇的属性  传地址  指针才能找到相关位置
		Sleep(ps->_sleep_time);
	} while (ps->_status==OK);   //游戏状态OK 才会继续
}

void GameEnd(pSnake ps)
{
	SetPos(7, 8);    //定位 进行输出
	//检测游戏状态
	switch (ps->_status)
	{
	case END_NORMAL:
		wprintf(L"您退出了游戏！\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"哎呀  |-_-|| 撞墙了,下次要加油哦，游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"哎呀  ||-_-| 撞到自己了，下次要小心哦，游戏结束\n");
		break;
	}
	//进行蛇身链表的释放（找蛇头）
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		//保证能找到下一个节点
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}
